package base;

import java.awt.geom.Point2D;
import java.io.Serializable;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.logging.Level;


public /*abstract*/ class Ship implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -2393774580692125254L;

	/** The {@link Logger} for this class. */
    private static final Logger logger =
        Logger.getLogger(Ship.class.getName());
    
    public Ship(int guid, int typeid, double x, double y, double hp) {
    	this.guid = guid;
    	this.typeid = typeid;
    	this.x = x;
    	this.y = y;
    	
    	this.hp = hp;
    	
    	this.plannedX = Double.NaN;
    	this.plannedY = Double.NaN;
    }
    
    public boolean equals(Object another) {
    	if (another instanceof Ship == false) {
    		return false;
    	}
    	Ship s = (Ship) another;
    	return (s.getGuid() == this.getGuid());
    }
    
    public int hashCode()
    {
    	return guid;
    }
    
    private Vector<Turret> turretsVec = new Vector<Turret>();;
    
    private int guid = 0;
    private int typeid = 0;
    
    public void attachTurret(Turret turret) {
    	turretsVec.add(turret);
    }
    
    public Turret getTurret(int index) {
    	return turretsVec.get(index);
    }
    
    public int getNumTurrets() {
    	return turretsVec.size();
    }
    
    private double velocity = 0; // units/sec
	private double heading = 0;
	
	private double acceleration = 0;
	private double rotation = 0;
	
	private double plannedHeading = 0;
	private double plannedVelocity = 0;
	private double plannedX = Double.NaN;
	private double plannedY = Double.NaN;
	
	private double maxRotation = 30;   // Degrees ship can turn in 1 second
	private double maxAcceleration = 15;   // Ship acceleration in units/sec/sec
	private double maxVelocity  = 10; // Maximum ship velocity in units/sec
	
	private double x = 0;
	private double y = 0;
	
	private double hp = 0;
	
	public static int getNextIndex(int index)
	{
		if (index >= 3)
		{
			return 0;
		}
		return index + 1;
	}
	
	public static int getPrevIndex(int index)
	{
		if (index <= 0)
		{
			return 3;
		}
		return index - 1;
	}
	
	/**
	 * Returns the (x,y) for each corner of the ship, counting the location and the heading of the ship into account.
	 * 
	 * Quite heavy operation: 4*sin + 4*cos.
	 * 
	 * @return
	 */
	public Point2D.Double [] getRect()
	{
	//	logger.log(Level.INFO, "Corner angle {0} r {1}",
    //            new Object[]{cAngle, r});
		
		Point2D.Double [] corners = new Point2D.Double [4];
		for (int i = 0; i < 4; i++)
		{
			//corners[i] = new Point2D.Double(getBoxX(i), getBoxY(i));
			corners[i] = getCorner(i);
		}
		
		/*
		logger.log(Level.INFO, "Corners in order ({0}, {1}) ({2}, {3}) ({4}, {5}) ({6}, {7})",
                new Object[]{corners[0].x, corners[0].y, corners[1].x, corners[1].y, 
							 corners[2].x, corners[2].y, corners[3].x, corners[3].y});
		*/
		
		return corners;
	}
	/*
	public double getBoxX(int index)
	{
		return x + getBoxXlocal(index);
	}
	
	public double getBoxY(int index)
	{
		return y + getBoxYlocal(index);
	}
	*/
	
	public static double getWidth()
	{
		return 4;
	}
	
	public static double getLength()
	{
		return 20;
	}
	
	protected Point2D.Double getCorner(int index)
	{
		double cornerAngle = heading;
		
		switch (index) {
		case 0:
			cornerAngle += cAngle;
			break;
		case 1:
			cornerAngle -= cAngle;
			break;
		case 2:
			cornerAngle += (180 + cAngle);
			break;
		case 3:
			cornerAngle += (180 - cAngle);
			break;
		default:
			return null;
		}
		
	//	logger.log(Level.INFO, "angle for corner {0} is {1}",
    //            new Object[]{index, cornerAngle});
		
		return new Point2D.Double ( x + r * Geometry.getX(cornerAngle), 
								    y + r * Geometry.getY(cornerAngle));
	}
	
	/*
	protected Point2D.Double getCorner(int index)
	{
		double width, length;
		width  = getWidth() / 2;
		length = getLength() / 2;
		double rx, ry;
		double cos = Geometry.getX(heading);
		double sin = Geometry.getY(heading);
		
		switch (index) {
		case 0:
			rx = width * cos - length * sin;
			ry = length * cos + width * sin;
			break;
		case 1:
			rx = -width * cos - length * sin;
			ry = length * cos - width * sin;
			break;
		case 2:
			rx = -width * cos + length * sin;
			ry = -length * cos - width * sin;
			break;
		case 3:
			rx = width * cos + length * sin;
			ry = -length * cos + width * sin;
			break;
		default:
			return null;
		}
		
		return new Point2D.Double ( x + rx, 
								    y + ry);
	}
	*/
	// this version comes exactly from
	// http://stackoverflow.com/questions/644378/drawing-a-rotated-rectangle
	/*
	protected Point2D.Double getCorner(int index)
	{
		double width, length;
		width  = getWidth() / 2;
		length = getLength() / 2;
		double rx, ry;
		double cos = Geometry.getX(heading);
		double sin = Geometry.getY(heading);
		
		switch (index) {
		case 0:
			rx = width * cos - length * sin;
			ry = length * cos + width * sin;
			break;
		case 1:
			rx = -width * cos - length * sin;
			ry = length * cos - width * sin;
			break;
		case 2:
			rx = -width * cos + length * sin;
			ry = -length * cos - width * sin;
			break;
		case 3:
			rx = width * cos + length * sin;
			ry = -length * cos + width * sin;
			break;
		default:
			return null;
		}
		
		return new Point2D.Double (  (x + rx), 
								    (y + ry));
	}
	*/
	
	/*
	protected Point2D.Double getCorner(int index)
	{
		double width, length;
		switch (index) {
		case 0:
			width  = - getWidth() / 2;
			length = getLength() / 2;
			break;
		case 1:
			width  = getWidth() / 2;
			length = getLength() / 2;
			break;
		case 2:
			width  = getWidth() / 2;
			length = - getLength() / 2;
			break;
		case 3:
			width  = - getWidth() / 2;
			length = - getLength() / 2;
			break;
		default:
			return null;
		}
		double cos = Geometry.getX(heading);
		double sin = Geometry.getY(heading);
		return new Point2D.Double (  (x + width * cos + length * sin), 
								    (y + length * cos + width * sin));
	}
	*/
	
	private static final double r = Math.sqrt(Math.pow(getLength()/2, 2) + Math.pow(getWidth()/2, 2));
	private static final double cAngle = Geometry.getHeading(0, 0,  getLength()/2, getWidth() /2 );
	
	/*
	 * functions to return local coordinates for the corners of the square 
	 */
	
	/*
	protected double getBoxXlocal(int index)
	{
		double boxx = getWidth();
		double boxy = getLength();
		if (index == 0 || index == 3) {
			//boxx = -9.98066711426;
			boxx = -boxx;
		}
		else if (index == 1 || index == 2) {
			//boxx = 10.390786171;
		}
		else {
			boxx = Double.NaN;
		}
		
		if (index == 3 || index == 2) {
			//boxy = -2.00112080574;
			boxy = -boxy;
		}
		
		return Geometry.getX(heading) * boxx + Geometry.getY(heading + 90) * boxy;
	}
	
	
	protected double getBoxYlocal(int index) {
		
		double boxx = getWidth();
		if (index == 0 || index == 3) {
			//boxx = -9.98066711426;
			boxx = -boxx;
		}
		
		double boxy = getLength();
		if (index == 0 || index == 1) {
			//boxy = 2.02804708481;
		}
		else if (index == 3 || index == 2) {
			//boxy = -2.00112080574;
			boxy = -boxy;
		}
		else {
			boxy = Double.NaN;
		}
		
		return Geometry.getY(heading) * boxy + Geometry.getX(heading - 90) * boxx;
	}
	*/
	
	public double getX()
	{
		return x;
	}
	
	public void setX(double newX)
	{
		x = newX;
	}
	
	public double getY()
	{
		return y;
	}
	
	public void setY(double newY)
	{
		y = newY;
	}

	public void setMoveTarget(double x, double y)
	{
		logger.log(Level.INFO, "Recieved order to move to ({0}, {1})",
                new Object[]{x, y});
		plannedX = x;
		plannedY = y;
		
	}
	
	public double getHeading()
	{
		return heading;
	}
	
	public void setHeading(double h)
	{
		heading = Geometry.ensurePositive(h);
	}
	
	public double getVelocity() {
		return velocity;
	}

	public void setVelocity(double velocity) {
		this.velocity = velocity;
	}

	public double getAcceleration() {
		return acceleration;
	}

	public void setAcceleration(double acceleration) {
		this.acceleration = acceleration;
	}

	public double getRotation() {
		return rotation;
	}

	public void setRotation(double rotation) {
		this.rotation = rotation;
	}

	public double getPlannedHeading() {
		return plannedHeading;
	}

	public void setPlannedHeading(double plannedHeading) {
		this.plannedHeading = plannedHeading;
	}

	public double getPlannedVelocity() {
		return plannedVelocity;
	}

	public void setPlannedVelocity(double plannedVelocity) {
		this.plannedVelocity = plannedVelocity;
	}

	public double getPlannedX() {
		return plannedX;
	}

	public void setPlannedX(double plannedX) {
		this.plannedX = plannedX;
	}

	public double getPlannedY() {
		return plannedY;
	}

	public void setPlannedY(double plannedY) {
		this.plannedY = plannedY;
	}

	public int getGuid() {
		return guid;
	}

	public int getTypeid() {
		return typeid;
	}

	public double getMaxRotation() {
		return maxRotation;
	}

	public double getMaxAcceleration() {
		return maxAcceleration;
	}

	public double getMaxVelocity() {
		return maxVelocity;
	}
	
	public double getMinVelocity() {
		return 0;
	}

	public double getHp() {
		return hp;
	}

	public void reduceHp(double reduction) {
		this.hp -= hp;
	}

	public Point2D.Double getPos() {
		return new Point2D.Double(getX(), getY());
	}
	
	/**
	 * Checks whether this ship and the other ship collide.
	 * 
	 * @param another
	 * @return true iff this ship collides with the other ship
	 */
	public boolean collides(Ship another)
	{
		double distSq = Geometry.getDistanceSquared(getPos(), another.getPos());
		double lengthSq = Math.max(Ship.getLength(), Ship.getWidth());
		lengthSq *= lengthSq;
		if (distSq > lengthSq) {
			return false;
		}
		
		return Geometry.intersectsRect(this.getRect(), another.getRect());
	}

}
